/**
 * @fileoverview Tests for HTML reporter.
 * @author Julian Laval
 */

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const assert = require("chai").assert;
const formatter = require("../../../../lib/cli-engine/formatters/html");
const cheerio = require("cheerio");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
 * Run unit tests on the overview section
 * @param {Object} $ Cheerio instance
 * @param {Object} args Array of relevant info to be tested
 * @returns {void}
 */
function checkOverview($, args) {
	assert($("#overview").hasClass(args.bgColor), "Check if color is correct");
	assert.strictEqual(
		$("#overview span").text(),
		args.problems,
		"Check if correct problem totals",
	);
}

/**
 * Run unit tests on the header section
 * @param {Object} $ Cheerio instance
 * @param {Object} rowObject Header row being tested
 * @param {Object} args Array of relevant info to be tested
 * @returns {void}
 */
function checkHeaderRow($, rowObject, args) {
	const row = $(rowObject);

	assert(
		row.hasClass(args.bgColor),
		"Check that background color is correct",
	);
	assert.strictEqual(
		row.attr("data-group"),
		args.group,
		"Check that header group is correct",
	);
	assert.strictEqual(
		row.find("th span").text(),
		args.problems,
		"Check if correct totals",
	);
	assert.strictEqual(
		row
			.find("th")
			.html()
			.trim()
			.match(/ [^<]*/u)[0]
			.trim(),
		args.file,
		"Check if correctly displays filePath",
	);
}

/**
 * Run unit tests on the content section
 * @param {Object} $ Cheerio instance
 * @param {Object} rowObject Content row being tested
 * @param {Object} args Array of relevant info to be tested
 * @returns {void}
 */
function checkContentRow($, rowObject, args) {
	const row = $(rowObject);

	assert(row.hasClass(args.group), "Check that linked to correct header");
	assert.strictEqual(
		$(row.find("td")[0]).text(),
		args.lineCol,
		"Check that line:column is correct",
	);
	assert(
		$(row.find("td")[1]).hasClass(args.color),
		"Check that severity color is correct",
	);
	assert.strictEqual(
		$(row.find("td")[2]).html(),
		args.message,
		"Check that message is correct",
	);
	assert.strictEqual(
		$(row.find("td")[3]).find("a").text(),
		args.ruleId,
		"Check that ruleId is correct",
	);
}

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

describe("formatter:html", () => {
	describe("when passed a single error message", () => {
		const rulesMeta = {
			foo: {
				type: "problem",

				docs: {
					description: "This is rule 'foo'",
					category: "error",
					recommended: true,
					url: "https://eslint.org/docs/rules/foo",
				},

				fixable: "code",

				messages: {
					message1: "This is a message for rule 'foo'.",
				},
			},
		};
		const code = {
			results: [
				{
					filePath: "foo.js",
					errorCount: 1,
					warningCount: 0,
					messages: [
						{
							message: "Unexpected foo.",
							severity: 2,
							line: 5,
							column: 10,
							ruleId: "foo",
							source: "foo",
						},
					],
				},
			],
			rulesMeta,
		};

		it("should return a string in HTML format with 1 issue in 1 file and styled accordingly", () => {
			const result = formatter(code.results, { rulesMeta });
			const $ = cheerio.load(result);

			// Check overview
			checkOverview($, {
				bgColor: "bg-2",
				problems: "1 problem (1 error, 0 warnings)",
			});

			// Check rows
			assert.strictEqual(
				$("tr").length,
				2,
				"Check that there are two (1 header, 1 content)",
			);
			assert.strictEqual(
				$('tr[data-group|="f"]').length,
				1,
				"Check that is 1 header row (implying 1 content row)",
			);
			checkHeaderRow($, $("tr")[0], {
				bgColor: "bg-2",
				group: "f-0",
				file: "foo.js",
				problems: "1 problem (1 error, 0 warnings)",
			});
			checkContentRow($, $("tr")[1], {
				group: "f-0",
				lineCol: "5:10",
				color: "clr-2",
				message: "Unexpected foo.",
				ruleId: "foo",
			});
		});

		it("should not fail if metadata is not available", () => {
			const result = formatter(code.results);

			const $ = cheerio.load(result);

			// Check overview
			checkOverview($, {
				bgColor: "bg-2",
				problems: "1 problem (1 error, 0 warnings)",
			});

			// Check rows
			assert.strictEqual(
				$("tr").length,
				2,
				"Check that there are two (1 header, 1 content)",
			);
			assert.strictEqual(
				$('tr[data-group|="f"]').length,
				1,
				"Check that is 1 header row (implying 1 content row)",
			);
			checkHeaderRow($, $("tr")[0], {
				bgColor: "bg-2",
				group: "f-0",
				file: "foo.js",
				problems: "1 problem (1 error, 0 warnings)",
			});
			checkContentRow($, $("tr")[1], {
				group: "f-0",
				lineCol: "5:10",
				color: "clr-2",
				message: "Unexpected foo.",
				ruleId: "foo",
			});
		});
	});

	describe("when passed a single warning message", () => {
		const rulesMeta = {
			foo: {
				type: "problem",

				docs: {
					description: "This is rule 'foo'",
					category: "error",
					recommended: true,
					url: "https://eslint.org/docs/rules/foo",
				},

				fixable: "code",

				messages: {
					message1: "This is a message for rule 'foo'.",
				},
			},
		};
		const code = {
			results: [
				{
					filePath: "foo.js",
					errorCount: 0,
					warningCount: 1,
					messages: [
						{
							message: "Unexpected foo.",
							severity: 1,
							line: 5,
							column: 10,
							ruleId: "foo",
							source: "foo",
						},
					],
				},
			],
			rulesMeta,
		};

		it("should return a string in HTML format with 1 issue in 1 file and styled accordingly", () => {
			const result = formatter(code.results, { rulesMeta });
			const $ = cheerio.load(result);

			// Check overview
			checkOverview($, {
				bgColor: "bg-1",
				problems: "1 problem (0 errors, 1 warning)",
			});

			// Check rows
			assert.strictEqual(
				$("tr").length,
				2,
				"Check that there are two (1 header, 1 content)",
			);
			assert.strictEqual(
				$('tr[data-group|="f"]').length,
				1,
				"Check that is 1 header row (implying 1 content row)",
			);
			checkHeaderRow($, $("tr")[0], {
				bgColor: "bg-1",
				group: "f-0",
				file: "foo.js",
				problems: "1 problem (0 errors, 1 warning)",
			});
			checkContentRow($, $("tr")[1], {
				group: "f-0",
				lineCol: "5:10",
				color: "clr-1",
				message: "Unexpected foo.",
				ruleId: "foo",
			});
		});
	});

	describe("when passed a single error message", () => {
		const rulesMeta = {
			foo: {
				type: "problem",

				docs: {
					description: "This is rule 'foo'",
					category: "error",
					recommended: true,
					url: "https://eslint.org/docs/rules/foo",
				},

				fixable: "code",

				messages: {
					message1: "This is a message for rule 'foo'.",
				},
			},
		};
		const code = {
			results: [
				{
					filePath: "foo.js",
					errorCount: 1,
					warningCount: 0,
					messages: [
						{
							message: "Unexpected foo.",
							severity: 2,
							line: 5,
							column: 10,
							ruleId: "foo",
							source: "foo",
						},
					],
				},
			],
			rulesMeta,
		};

		it("should return a string in HTML format with 1 issue in 1 file and styled accordingly", () => {
			const result = formatter(code.results, { rulesMeta });
			const $ = cheerio.load(result);

			// Check overview
			checkOverview($, {
				bgColor: "bg-2",
				problems: "1 problem (1 error, 0 warnings)",
			});

			// Check rows
			assert.strictEqual(
				$("tr").length,
				2,
				"Check that there are two (1 header, 1 content)",
			);
			assert.strictEqual(
				$('tr[data-group|="f"]').length,
				1,
				"Check that is 1 header row (implying 1 content row)",
			);
			checkHeaderRow($, $("tr")[0], {
				bgColor: "bg-2",
				group: "f-0",
				file: "foo.js",
				problems: "1 problem (1 error, 0 warnings)",
			});
			checkContentRow($, $("tr")[1], {
				group: "f-0",
				lineCol: "5:10",
				color: "clr-2",
				message: "Unexpected foo.",
				ruleId: "foo",
			});
		});
	});

	describe("when passed no error/warning messages", () => {
		const code = {
			results: [
				{
					filePath: "foo.js",
					errorCount: 0,
					warningCount: 0,
					messages: [],
				},
			],
		};

		it("should return a string in HTML format with 0 issues in 1 file and styled accordingly", () => {
			const result = formatter(code.results, {});
			const $ = cheerio.load(result);

			// Check overview
			checkOverview($, { bgColor: "bg-0", problems: "0 problems" });

			// Check rows
			assert.strictEqual(
				$("tr").length,
				1,
				"Check that there is 1 row (header)",
			);
			checkHeaderRow($, $("tr")[0], {
				bgColor: "bg-0",
				group: "f-0",
				file: "foo.js",
				problems: "0 problems",
			});
		});
	});

	describe("when passed multiple messages", () => {
		const rulesMeta = {
			foo: {
				type: "problem",

				docs: {
					description: "This is rule 'foo'",
					category: "error",
					recommended: true,
					url: "https://eslint.org/docs/rules/foo",
				},

				fixable: "code",

				messages: {
					message1: "This is a message for rule 'foo'.",
				},
			},
			bar: {
				type: "suggestion",

				docs: {
					description: "This is rule 'bar'",
					category: "error",
					recommended: false,
				},

				messages: {
					message1: "This is a message for rule 'bar'.",
				},
			},
		};
		const code = {
			results: [
				{
					filePath: "foo.js",
					errorCount: 1,
					warningCount: 1,
					messages: [
						{
							message: "Unexpected foo.",
							severity: 2,
							line: 5,
							column: 10,
							ruleId: "foo",
							source: "foo",
						},
						{
							message: "Unexpected bar.",
							severity: 1,
							line: 6,
							column: 11,
							ruleId: "bar",
							source: "bar",
						},
					],
				},
			],
			rulesMeta,
		};

		it("should return a string in HTML format with 2 issues in 1 file and styled accordingly", () => {
			const result = formatter(code.results, { rulesMeta });
			const $ = cheerio.load(result);

			// Check overview
			checkOverview($, {
				bgColor: "bg-2",
				problems: "2 problems (1 error, 1 warning)",
			});

			// Check rows
			assert.strictEqual(
				$("tr").length,
				3,
				"Check that there are two (1 header, 2 content)",
			);
			assert.strictEqual(
				$('tr[data-group|="f"]').length,
				1,
				"Check that is 1 header row (implying 2 content row)",
			);
			checkHeaderRow($, $("tr")[0], {
				bgColor: "bg-2",
				group: "f-0",
				file: "foo.js",
				problems: "2 problems (1 error, 1 warning)",
			});
			checkContentRow($, $("tr")[1], {
				group: "f-0",
				lineCol: "5:10",
				color: "clr-2",
				message: "Unexpected foo.",
				ruleId: "foo",
			});
			checkContentRow($, $("tr")[2], {
				group: "f-0",
				lineCol: "6:11",
				color: "clr-1",
				message: "Unexpected bar.",
				ruleId: "bar",
			});
		});
	});

	describe("when passed multiple files with 1 error & warning message respectively", () => {
		const rulesMeta = {
			foo: {
				type: "problem",

				docs: {
					description: "This is rule 'foo'",
					category: "error",
					recommended: true,
					url: "https://eslint.org/docs/rules/foo",
				},

				fixable: "code",

				messages: {
					message1: "This is a message for rule 'foo'.",
				},
			},
			bar: {
				type: "suggestion",

				docs: {
					description: "This is rule 'bar'",
					category: "error",
					recommended: false,
				},

				messages: {
					message1: "This is a message for rule 'bar'.",
				},
			},
		};
		const code = {
			results: [
				{
					filePath: "foo.js",
					errorCount: 1,
					warningCount: 0,
					messages: [
						{
							message: "Unexpected foo.",
							severity: 2,
							line: 5,
							column: 10,
							ruleId: "foo",
							source: "foo",
						},
					],
				},
				{
					filePath: "bar.js",
					errorCount: 0,
					warningCount: 1,
					messages: [
						{
							message: "Unexpected bar.",
							severity: 1,
							line: 6,
							column: 11,
							ruleId: "bar",
							source: "bar",
						},
					],
				},
			],
			rulesMeta,
		};

		it("should return a string in HTML format with 2 issues in 2 files and styled accordingly", () => {
			const result = formatter(code.results, { rulesMeta });
			const $ = cheerio.load(result);

			// Check overview
			checkOverview($, {
				bgColor: "bg-2",
				problems: "2 problems (1 error, 1 warning)",
			});

			// Check rows
			assert.strictEqual(
				$("tr").length,
				4,
				"Check that there are two (2 header, 2 content)",
			);
			assert.strictEqual(
				$('tr[data-group|="f"]').length,
				2,
				"Check that is 2 header row (implying 2 content row)",
			);
			checkHeaderRow($, $("tr")[0], {
				bgColor: "bg-2",
				group: "f-0",
				file: "foo.js",
				problems: "1 problem (1 error, 0 warnings)",
			});
			checkContentRow($, $("tr")[1], {
				group: "f-0",
				lineCol: "5:10",
				color: "clr-2",
				message: "Unexpected foo.",
				ruleId: "foo",
			});
			checkHeaderRow($, $("tr")[2], {
				bgColor: "bg-1",
				group: "f-1",
				file: "bar.js",
				problems: "1 problem (0 errors, 1 warning)",
			});
			checkContentRow($, $("tr")[3], {
				group: "f-1",
				lineCol: "6:11",
				color: "clr-1",
				message: "Unexpected bar.",
				ruleId: "bar",
			});
		});
	});

	describe("when passed multiple files with 1 warning message each", () => {
		const rulesMeta = {
			foo: {
				type: "problem",

				docs: {
					description: "This is rule 'foo'",
					category: "error",
					recommended: true,
					url: "https://eslint.org/docs/rules/foo",
				},

				fixable: "code",

				messages: {
					message1: "This is a message for rule 'foo'.",
				},
			},
			bar: {
				type: "suggestion",

				docs: {
					description: "This is rule 'bar'",
					category: "error",
					recommended: false,
				},

				messages: {
					message1: "This is a message for rule 'bar'.",
				},
			},
		};
		const code = {
			results: [
				{
					filePath: "foo.js",
					errorCount: 0,
					warningCount: 1,
					messages: [
						{
							message: "Unexpected foo.",
							severity: 1,
							line: 5,
							column: 10,
							ruleId: "foo",
							source: "foo",
						},
					],
				},
				{
					filePath: "bar.js",
					errorCount: 0,
					warningCount: 1,
					messages: [
						{
							message: "Unexpected bar.",
							severity: 1,
							line: 6,
							column: 11,
							ruleId: "bar",
							source: "bar",
						},
					],
				},
			],
			rulesMeta,
		};

		it("should return a string in HTML format with 2 issues in 2 files and styled accordingly", () => {
			const result = formatter(code.results, { rulesMeta });
			const $ = cheerio.load(result);

			// Check overview
			checkOverview($, {
				bgColor: "bg-1",
				problems: "2 problems (0 errors, 2 warnings)",
			});

			// Check rows
			assert.strictEqual(
				$("tr").length,
				4,
				"Check that there are two (2 header, 2 content)",
			);
			assert.strictEqual(
				$('tr[data-group|="f"]').length,
				2,
				"Check that is 2 header row (implying 2 content row)",
			);
			checkHeaderRow($, $("tr")[0], {
				bgColor: "bg-1",
				group: "f-0",
				file: "foo.js",
				problems: "1 problem (0 errors, 1 warning)",
			});
			checkContentRow($, $("tr")[1], {
				group: "f-0",
				lineCol: "5:10",
				color: "clr-1",
				message: "Unexpected foo.",
				ruleId: "foo",
			});
			checkHeaderRow($, $("tr")[2], {
				bgColor: "bg-1",
				group: "f-1",
				file: "bar.js",
				problems: "1 problem (0 errors, 1 warning)",
			});
			checkContentRow($, $("tr")[3], {
				group: "f-1",
				lineCol: "6:11",
				color: "clr-1",
				message: "Unexpected bar.",
				ruleId: "bar",
			});
		});
	});

	describe("when passing a single message with illegal characters", () => {
		const rulesMeta = {
			foo: {
				type: "problem",

				docs: {
					description: "This is rule 'foo'",
					category: "error",
					recommended: true,
					url: "https://eslint.org/docs/rules/foo",
				},

				fixable: "code",

				messages: {
					message1: "This is a message for rule 'foo'.",
				},
			},
		};
		const code = {
			results: [
				{
					filePath: "foo.js",
					errorCount: 1,
					warningCount: 0,
					messages: [
						{
							message: "Unexpected <&\"'> foo.",
							severity: 2,
							line: 5,
							column: 10,
							ruleId: "foo",
							source: "foo",
						},
					],
				},
			],
			rulesMeta,
		};

		it("should return a string in HTML format with 1 issue in 1 file", () => {
			const result = formatter(code.results, { rulesMeta });
			const $ = cheerio.load(result);

			checkContentRow($, $("tr")[1], {
				group: "f-0",
				lineCol: "5:10",
				color: "clr-2",
				message: "Unexpected &lt;&amp;&quot;&apos;&gt; foo.",
				ruleId: "foo",
			});
		});
	});

	describe("when passing a single message with no rule id or message", () => {
		const code = [
			{
				filePath: "foo.js",
				errorCount: 1,
				warningCount: 0,
				messages: [
					{
						severity: 2,
						line: 5,
						column: 10,
					},
				],
			},
		];

		it("should return a string in HTML format with 1 issue in 1 file", () => {
			const result = formatter(code, {});
			const $ = cheerio.load(result);

			checkContentRow($, $("tr")[1], {
				group: "f-0",
				lineCol: "5:10",
				color: "clr-2",
				message: "",
				ruleId: "",
			});
		});
	});

	describe("when passed a single message with no line or column", () => {
		const rulesMeta = {
			foo: {
				type: "problem",

				docs: {
					description: "This is rule 'foo'",
					category: "error",
					recommended: true,
					url: "https://eslint.org/docs/rules/foo",
				},

				fixable: "code",

				messages: {
					message1: "This is a message for rule 'foo'.",
				},
			},
		};
		const code = {
			results: [
				{
					filePath: "foo.js",
					errorCount: 1,
					warningCount: 0,
					messages: [
						{
							message: "Unexpected foo.",
							severity: 2,
							ruleId: "foo",
							source: "foo",
						},
					],
				},
			],
			rulesMeta,
		};

		it("should return a string in HTML format with 1 issue in 1 file and styled accordingly", () => {
			const result = formatter(code.results, { rulesMeta });
			const $ = cheerio.load(result);

			checkContentRow($, $("tr")[1], {
				group: "f-0",
				lineCol: "0:0",
				color: "clr-2",
				message: "Unexpected foo.",
				ruleId: "foo",
			});
		});
	});
});
